package ee.az.test.gui;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.ToolTipManager;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;

/**
 * @author Artur Zinatullin (artur.zinatullin@gmail.com) 2010
 */
public class Glass extends JFrame {

	private static final Color EMPTY = Color.darkGray;
	private static final Color FLOATING = Color.white;
	private static final Color FIXED = Color.lightGray;

	private static final long serialVersionUID = 1L;

	private final static int WIDTH = 16;
	private final static int HEIGHT = 25;

	private TableModel dataModel;
	private FallWorker fallWorker;

	public Glass() {
		JTable table = createTable();
		add(table);
		setSize(table.getPreferredSize());
		table.addKeyListener(new KeyListener());
		initGlass(dataModel);
		Emitter emitter = new Emitter();
		fallWorker = new FallWorker(emitter);
		emitter.fixed();
	}

	public static void main(String[] args) {
		try {
			Glass frame = new Glass();
			frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			frame.setTitle("Table test");
			frame.pack();
			frame.setResizable(false);
			frame.setVisible(true);
		} catch (Exception e) {
			System.exit(1);
		}
	}
	private JTable createTable() {
		dataModel = new DefaultTableModel() {
			private static final long serialVersionUID = 1L;
			@Override
			public int getColumnCount() { return WIDTH; }
			@Override
			public int getRowCount() { return HEIGHT;}
		};
		JTable table = new JTable(dataModel);
		table.setTableHeader(null);
		table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		table.setRowSelectionAllowed(false);
		table.setCellSelectionEnabled(false);
		setColumnsWidth(table);
		ToolTipManager.sharedInstance().unregisterComponent(table);
		return table;
	}

	private void setColumnsWidth(JTable table) {
		TableColumn column = null;
		for (int i = 0; i < table.getModel().getColumnCount(); i++) {
			column = table.getColumnModel().getColumn(i);
			column.setPreferredWidth(15);
			column.setCellRenderer(new ColorRenderer());
		}

	}

	private class ColorRenderer extends DefaultTableCellRenderer {
		private static final long serialVersionUID = 1L;

		public ColorRenderer() {
			setHorizontalAlignment(JLabel.CENTER);
		}

		@Override
		public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
			Component tableCellRendererComponent = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
			if (value instanceof Color) {
				setBackground((Color)value);
			}
			setText("");
			return tableCellRendererComponent;
		}
	}

	private void initGlass(TableModel model) {
		for (int col = 0; col < model.getColumnCount(); col++) {
			for (int row = 0; row < model.getRowCount(); row++) {
				model.setValueAt(EMPTY, row, col);
			}
		}
	}

	private abstract class Worker {
		public abstract void work(TableModel model);

		protected boolean isAvaliable(int row, int col, TableModel model) {
			if (row < 0 || row >= model.getRowCount()) {
				return false;
			} else if (col < 0 || col >= model.getColumnCount()) {
				return false;
			} else {
				return EMPTY.equals(model.getValueAt(row, col)) || FLOATING.equals(model.getValueAt(row, col));
			}
		}
	}

	private class FallWorker extends Worker {
		
		private FixListener listener;
		
		public FallWorker(FixListener listener) {
			this.listener = listener;
		}

		@Override
		public void work(TableModel model) {
			boolean canFall = true;
			for (int col = 0; col < model.getColumnCount(); col++) {
				for (int row = model.getRowCount()-1; row >= 0 ; row--) {
					Color value = (Color) dataModel.getValueAt(row, col);
					if (FLOATING.equals(value)) {
						if (!isAvaliable(row+1, col, model)) {
							canFall = false;
						}
					}
				}
			}
			if (canFall) { // then fall
				for (int col = 0; col < model.getColumnCount(); col++) {
					for (int row = model.getRowCount()-1; row >= 0 ; row--) {
						Color value = (Color) model.getValueAt(row, col);
						if (FLOATING.equals(value)) {
							model.setValueAt(value, row+1, col);
							model.setValueAt(EMPTY, row, col);
						}
					}
				}
			} else { // then fix
				for (int col = 0; col < model.getColumnCount(); col++) {
					for (int row = model.getRowCount()-1; row >= 0 ; row--) {
						Color value = (Color) model.getValueAt(row, col);
						if (FLOATING.equals(value)) {
							model.setValueAt(FIXED, row, col);
						}
					}
				}
				listener.fixed();
			}
		}
	}
	private class LeftShiftWorker extends Worker {
		@Override
		public void work(TableModel model) {
			boolean canShift = true;
			for (int col = 0; col < model.getColumnCount(); col++) {
				for (int row = model.getRowCount()-1; row >= 0 ; row--) {
					Color value = (Color) dataModel.getValueAt(row, col);
					if (FLOATING.equals(value)) {
						if (!isAvaliable(row, col-1, model)) {
							canShift = false;
						}
					}
				}
			}
			if (canShift) { // then fall
				for (int col = 0; col < model.getColumnCount(); col++) {
					for (int row = model.getRowCount()-1; row >= 0 ; row--) {
						Color value = (Color) model.getValueAt(row, col);
						if (FLOATING.equals(value)) {
							model.setValueAt(value, row, col-1);
							model.setValueAt(EMPTY, row, col);
						}
					}
				}				}
		}
	}

	private class RightShiftWorker extends Worker {
		@Override
		public void work(TableModel model) {
			boolean canShift = true;
			for (int col = model.getColumnCount()-1; col >= 0; col--) {
				for (int row = model.getRowCount()-1; row >= 0 ; row--) {
					Color value = (Color) dataModel.getValueAt(row, col);
					if (FLOATING.equals(value)) {
						if (!isAvaliable(row, col+1, model)) {
							canShift = false;
						}
					}
				}
			}
			if (canShift) { // then fall
				for (int col = model.getColumnCount()-1; col >= 0; col--) {
					for (int row = model.getRowCount()-1; row >= 0 ; row--) {
						Color value = (Color) model.getValueAt(row, col);
						if (FLOATING.equals(value)) {
							model.setValueAt(value, row, col+1);
							model.setValueAt(EMPTY, row, col);
						}
					}
				}
			}
		}
	}

	private class KeyListener extends KeyAdapter {
		@Override
		public void keyPressed(KeyEvent e) {
			int keyCode = e.getKeyCode();
			switch(keyCode) {
				case 37:
					new LeftShiftWorker().work(dataModel);
					break;
				case 38: break;
				case 39:
					new RightShiftWorker().work(dataModel);
					break;
				case 40:
					fallWorker.work(dataModel);
					break;
			}
			System.out.println(keyCode);
		}
	}
	
	private interface FixListener {
		public void fixed();
	}
	
	private class Emitter implements FixListener {
		public void fixed() {
			dataModel.setValueAt(FLOATING, 0, dataModel.getColumnCount()/2);
		}
	}
}
